package edu.stanford.travelhard.audio;

import java.io.IOException;

import edu.stanford.travelhard.Annotate;
import edu.stanford.travelhard.camera.Util;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;

public class AudioManager {
	
	private String        mAudioFilename;
	private Context       mContext;
	private Handler       mHandler;
	private MediaRecorder mAudioRecorder;
	private MediaPlayer   mAudioPlayer;
	private boolean       mIsRecording;
	private boolean       mIsPlaying;
	
	// Threads to update the UI
	private Thread mPlaybackBarUpdate;
	private Thread mRecordTimerUpdate;
	
	// This is a hack, AAC only supported in 2.3+
	private static final int AAC = 3;
	
	private static final boolean DEBUG = true;
	
	public AudioManager (String filename, Context context, Handler handler) {
		mAudioFilename = filename;
		mContext = context;
		mHandler = handler;
		mAudioRecorder = null;
		mAudioPlayer = null;
		mIsRecording = false;
		mIsPlaying = false;
		mPlaybackBarUpdate = null;
		mRecordTimerUpdate = null;
	}
	
	/**
	 * Destroys this AudioManager when its owner is getting destroyed
	 * as well--otherwise we run into crazy issues.
	 */
	public void destroy() {
		// Get rid of backpointers to the owning activity
		// so that we don't leak its associated memory
		mContext = null;
		mHandler = null;
		
		// stop everything
		if (inPlayAudioMode()) {
			stopAudio();
			if (mPlaybackBarUpdate != null && mPlaybackBarUpdate.isAlive()) {
				// TODO: get it to die somehow?
			}
		}
		if (inRecordAudioMode()) {
			stopRecording();
			if (mRecordTimerUpdate != null && mRecordTimerUpdate.isAlive()) {
				// TODO: get it to die somehow?
			}
		}
		
	}
	
	/**
	 * Starts the audio player from scratch, creating associated
	 * resources. Also restarts the audio player if it is 
	 * currently paused. Must either be paused (in play mode and
	 * not in record mode) or not in either play more nor record
	 * mode.
	 */
	public void playAudio() {
		if (DEBUG) {
			Util.Assert((inPlayAudioMode() && !inRecordAudioMode() && !mAudioPlayer.isPlaying()) ||
					(!inPlayAudioMode() && !inRecordAudioMode()));
		}
		
		if (inPlayAudioMode() && !inRecordAudioMode() && !mAudioPlayer.isPlaying()) {
			mAudioPlayer.start();
		} else if (!inPlayAudioMode() && !inRecordAudioMode()) {
			mIsPlaying = true;
			mAudioPlayer = MediaPlayer.create(mContext, Uri.parse(mAudioFilename));
			mAudioPlayer.setVolume(1000, 1000);

			mAudioPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

				public void onCompletion(MediaPlayer mp) {
					stopAudio();
					mHandler.sendEmptyMessage(Annotate.PLAYBACK_FINISHED);
				}

			});
			mPlaybackBarUpdate = new Thread(new PlaybackBarUpdate());
			mPlaybackBarUpdate.start();
			mAudioPlayer.start();
		}
		
		if (DEBUG) Util.Assert(inPlayAudioMode() && !inRecordAudioMode());
	}
	
	/**
	 * Pauses the audio player. Must be in play mode and not
	 * in record mode, and the player must also be currently
	 * playing.
	 */
	public void pauseAudio() {
		if (DEBUG) Util.Assert(inPlayAudioMode() && !inRecordAudioMode() &&  mAudioPlayer.isPlaying());
		
		if (inPlayAudioMode() && !inRecordAudioMode() && mAudioPlayer.isPlaying()) {
			mAudioPlayer.pause();
		}
	}
	
	/**
	 * Stops the audio player and cleans up all associated
	 * resources (assumes mPlaybackBarUpdate thread will complete
	 * in near future). Must be in play mode and not in record
	 * mode.
	 */
	public void stopAudio() {
		if (DEBUG) Util.Assert(inPlayAudioMode() && !inRecordAudioMode());
		
		if (inPlayAudioMode() && !inRecordAudioMode()) {
			mIsPlaying = false;
			mAudioPlayer.stop();
			mAudioPlayer.release();
			mAudioPlayer = null;
		}
		
		if (DEBUG) Util.Assert(!inPlayAudioMode() && !inRecordAudioMode());
	}
	
	/**
	 * Moves the audio player to a particular location in the
	 * audio file. Audio player must be in play mode and not
	 * in record mode.
	 */
	public void seekTo(int location) {
		if (DEBUG) Util.Assert(inPlayAudioMode() && !inRecordAudioMode());
		
		if (inPlayAudioMode() && !inRecordAudioMode()) {
			mAudioPlayer.seekTo(location);
		}
	}
	
	/**
	 * Starts a new recording, creating associated resources.
	 * Must be not in either play mode nor record mode.
	 * @throws IOException 
	 */
	public void startRecording() throws IOException {
		if (DEBUG) Util.Assert(!inPlayAudioMode() && !inRecordAudioMode());
		
		if (!inPlayAudioMode() && !inRecordAudioMode()) {
			mIsRecording = true;

			mAudioRecorder = new MediaRecorder();
			mAudioRecorder.reset();
			mAudioRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
			mAudioRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
			mAudioRecorder.setOutputFile(mAudioFilename);
			mAudioRecorder.setAudioEncoder(AAC);
			mAudioRecorder.prepare();

			mRecordTimerUpdate = new Thread(new RecordTimerUpdate());
			mRecordTimerUpdate.start();
			
			mAudioRecorder.start();
		}
		
		if (DEBUG) Util.Assert(!inPlayAudioMode() && inRecordAudioMode());
	}

	/**
	 * Stops a recording that has been previously started
	 * and releases associated resources. Must be in
	 * record mode and not in play mode.
	 */
	public void stopRecording() {
		if (DEBUG) Util.Assert(!inPlayAudioMode() && inRecordAudioMode());
		
		if (!inPlayAudioMode() && inRecordAudioMode()) {
			mIsRecording = false;
			mAudioRecorder.stop();		
			mAudioRecorder.release();
			mAudioRecorder = null;
		}
		
		if (DEBUG) Util.Assert(!inPlayAudioMode() && !inRecordAudioMode());
	}
	
	/**
	 * Helper function to determine whether we are in
	 * play mode.
	 * @return True if in play mode, false otherwise.
	 */
	public boolean inPlayAudioMode() {
		return (mIsPlaying && mAudioPlayer != null &&
				!mIsRecording && mAudioRecorder == null);
	}
	
	/**
	 * Helper function to determine whether we are in
	 * record mode.
	 * @return True if in record mode, false otherwise.
	 */
	public boolean inRecordAudioMode() {
		return (mIsRecording && mAudioRecorder != null &&
				!mIsPlaying && mAudioPlayer == null);
	}
	
	/**
	 * 
	 * A thread that updates the owner's playback bar.
	 *
	 */
	private class PlaybackBarUpdate implements Runnable {

		public void run() {
			if (inPlayAudioMode() && !inRecordAudioMode()) {
				int previousPos = -1;
				int currentPos = 0;
				int total = mAudioPlayer.getDuration();
				while (inPlayAudioMode() && !inRecordAudioMode() && currentPos < total) {
					try {
						currentPos = mAudioPlayer.getCurrentPosition();
						if (currentPos != previousPos) {
							Message msg = mHandler.obtainMessage(Annotate.UPDATE_PLAYBACK_BAR);
							msg.arg1 = currentPos;
							msg.arg2 = total;
							mHandler.sendMessage(msg);
							previousPos = currentPos;
						}
						Thread.sleep(500);
					} catch (Exception e) {
						return;
					}
				}
			}
			mPlaybackBarUpdate = null;
		}

	}
	
	private class RecordTimerUpdate implements Runnable {

		public void run() {
			int min = 0;
			int seconds = 0;
			double millis = 0;
			String text;
			double t = System.currentTimeMillis();
			while (!inPlayAudioMode() && inRecordAudioMode()) {
				try {
					double currentT = System.currentTimeMillis();
					millis += (currentT - t);
					t = currentT;
					seconds = (int) Math.floor(millis / 1000.0);
					if (seconds >= 60) {
						min++;
						seconds -= 60;
						millis -= 60000;
					}
					text = String.valueOf(min);
					text += ":";
					if (seconds < 10) {
						text += "0";
						text += seconds;
					} else {
						text += seconds;
					}
					
					Message msg = mHandler.obtainMessage(Annotate.UPDATE_RECORD_TIMER);
					msg.obj = text;
					mHandler.sendMessage(msg);
					
					Thread.sleep(500);
				} catch (Exception e) {
					return;
				}
			}
			mRecordTimerUpdate = null;
		}
	}
}